package com.redhipps.hips.client.io;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestBuilder.Method;
import com.redhipps.hips.client.model.Context;
import com.redhipps.hips.client.model.PythonDatastoreKey;
import com.redhipps.hips.client.model.Model;

/**
 * Facility through which we implement request builders. This prevents
 * request URLs from being scattered throughout the code. Use this class
 * to create request builders as much as possible. 
 * 
 * @author yuhong
 */
public class RequestBuilderFactory {

  public enum Type {
    CONTEXT_ROOT("/ContextRoot", RequestBuilder.GET),
    MODEL_READ("/Read", RequestBuilder.GET, 1),
    MODEL_LIST("/List", RequestBuilder.GET, 1),
    MODEL_WRITE("/Write", RequestBuilder.POST, 1),
    MODEL_CREATE("/Create", RequestBuilder.POST, 1),
    ASSIGN_DOCTORS("/AssignDoctors", RequestBuilder.GET);

    private String request;
    private int expectedParams;
    private Method method;

    private Type(String request, Method method) {
      this(request, method, 0);
    }

    private Type(String request, Method method, int expectedParams) {
      this.request = request;
      this.method = method;
      this.expectedParams = expectedParams;
    }

    public String parse(String... params) {
      if (params.length != expectedParams) {
        throw new IllegalArgumentException("Expected " + expectedParams +
            " parameters but got " + params.length);
      }
      if (expectedParams == 0) {
        return request;
      } else {
        StringBuilder b = new StringBuilder();
        b.append(request);
        for (String s : params) {
          b.append(s);
        }
        return b.toString();
      }
    }

    public Method method() {
      return method;
    }

    public String toString() {
      return request;
    }
  }

  public RequestBuilderFactory() {}

  /**
   * Creates the specified type of {@link RequestBuilder}.
   * 
   * @param type Request builder type.
   * @param params Additional params for the request builder. Some types of readers
   *     require additional arguments to their specification. For instance, model
   *     readers and writers are typed using this param string.
   * @return RequestBuilder instance for specified params.
   */
  public RequestBuilder createBuilder(Type type, String... params) {
    return createBuilder(type, null, null, params);
  }

  /**
   * Creates a {@link RequestBuilder} for reading a class of models by context.
   * 
   * @param type Request builder type.
   * @param context Context in which to read.
   * @param params Additional params for the request builder.
   * @return RequestBuilder instance for specified params.
   */
  public RequestBuilder createBuilder(
      Type type, Context context, String... params) {
    return createBuilder(type, context, null, params);
  }

  /**
   * Creates a {@link RequestBuilder} for reading one specific model by key.
   * 
   * @param type Request builder type.
   * @param context Context in which to read.
   * @param modelKey Key for the model to read.
   * @param params Additional params for the request builder.
   * @return RequestBuilder instance for specified params.
   */
  public RequestBuilder createBuilder(
      Type type, Context context, PythonDatastoreKey modelKey, String... params) {
    String requestUrl = type.parse(params);
    if (context != null) {
      ParamFormatter f = new ParamFormatter();
      f.appendIfNotNull("login", context.loginKey());
      f.appendIfNotNull("institution", context.institution());
      f.appendIfNotNull("schedule", context.schedule());
      f.appendIfNotNull("doctor", context.doctor());
      f.appendIfNotNull("key", modelKey);
      requestUrl += f.toString();
    }
    if (GWT.isClient()) {
      return new RequestBuilder(type.method(), requestUrl);
    } else {
      // TODO Mock request builders?
      return new RequestBuilder(RequestBuilder.POST, requestUrl);
    }
  }

  private static class ParamFormatter {

    private StringBuilder b = new StringBuilder();

    public void appendIfNotNull(String s, Object value) {
      if (value != null) {
        if (value instanceof Model) {
          value = ((Model) value).key();
        }
        if (b.length() > 0) {
          b.append("&");
        }
        b.append(s);
        b.append("=");
        b.append(value.toString());
      }
    }

    @Override
    public String toString() {
      return "?" + b.toString();
    }
  }

}
